# -*- coding: utf-8 -*-
import os
import sys
import json
import codecs
import requests
from utils import Utils
# from lxml.etree import fromstring
# from lxml.cssselect import CSSSelector
from bs4 import BeautifulSoup

CONF = "github_projs.json"
SAVED_PATH = "github_projs"

class CodeFetch:
	URL = "https://github.com/search?o=desc&p={}&q=rust&s=stars&type=Repositories"

	def __init__(self, num):
		self.projs = {}
		self.__setup()
		self.download_all(num)

	def download_all(self, num):
		for n in range(num):
			page = self.URL.format(n + 1)
			projs = {}
			try:
				projs = self.__download_one(page)
			except:
				Utils.print_trace()
			finally:
				self.projs.update(projs)
				with open(CONF, "w+") as fp:
					json.dump(self.projs, fp, indent=4, separators=(',', ': '))

	def __download_one(self, page):
		Utils.notice_print(f"Start to download page: {page}")
		r = requests.get(page)
		bs = BeautifulSoup(r.text, "lxml")
		mnav = bs.select('div.mt-n1')
		projs = {}
		for item in mnav:
			desc = item.select("p.mb-1")[0].getText()
			link = item.select("a.v-align-middle")[0]['href']
			name = link.split("/")[-1]
			desc = desc.replace("\n", "")
			proj = {
				"name": name,
				"desc": desc.strip(),
				"link": "https://github.com" + link
			}
			projs[name] = proj
			self.__fetch_code(proj["link"])
		return projs

	def __setup(self):
		Utils.check_and_mkdir(SAVED_PATH)

	def __fetch_code(self, url):
		path = os.path.join(os.getcwd(), SAVED_PATH)
		cmd = "git clone {}".format(url + ".git")
		Utils.exec_command(cmd, cwd=path)

def download_codes():
	url = "https://github.com/search?o=desc&p={}&q=rust&s=stars&type=Repositories"
	pages = 30
	
	for p in range(pages):
		page_url = url.format(p + 1)
		r = requests.get(page_url)
		bs = BeautifulSoup(r.text, "lxml")
		mnav = bs.select('div.mt-n1')
		for item in mnav:
			desc = item.select("p.mb-1")[0].getText()
			link = item.select("a.v-align-middle")[0]['href']
			name = link.split("/")[-1]
			desc = desc.replace("\n", "")
			proj = {
				"name": name,
				"desc": desc.strip(),
				"link": "https://github.com" + link
			}
			print(proj)
		break

class CheckFile:
	"""
	stats {} -> file []
		-> fn, (fn, unsafe num)
	"""
	UNSAFE_LOG = "unsafe.log"

	def __init__(self, work_dir):
		self.stats = {}
		self.report = {}
		self.cur_proj = None
		self.__remove_log()
		self.scan_projs(work_dir)

	def scan_projs(self, work_dir):
		Utils.notice_print(f"Start to check rust files in: {work_dir}")
		projs = os.listdir(work_dir)
		# projs = ["claxon"]
		for proj in projs:
			self.cur_proj = proj
			cur_dir = os.path.join(os.getcwd(), work_dir, proj)
			self.__check_unsafe_in_proj(cur_dir)

	def __remove_log(self):
		Utils.remove_path(self.UNSAFE_LOG)

	def __check_unsafe_in_proj(self, di):
		for root, dirs, files in os.walk(di):
			for fi in files:
				if not fi.endswith(".rs"):
					continue
				file = os.path.join(root, fi)
				try:
					self.check_file(file)
				except:
					Utils.print_trace()

	def check_file(self, file):
		fn_stat = ["", 0]
		# print("check_file", file)
		with codecs.open(file, "r", "utf-8") as fp:
			Utils.log_to_file(self.UNSAFE_LOG, file + "\n")
			for idx, li in enumerate(fp):
				idx += 1
				li = li.strip()
				if li.startswith("//"):
					in_comment = True
				else:
					in_comment = False
				if li.startswith("/*"):
					in_comment = True
				if li.endswith("*/"):
					in_comment = False
				if in_comment:
					continue
				if " fn " in li or li.startswith("fn "):
					# print(idx, li, fn_stat)
					if fn_stat[0]:
						self.add_fn_to_file(file, fn_stat)
					fn_stat = [li, 0]
				if "unsafe " in li:
					fn_stat[1] += 1
					Utils.log_to_file(self.UNSAFE_LOG, "\t" + str(idx) + ": " + li + "\n")
					# print(idx, li)
					# print("uuuuusafe", li, file)
		if fn_stat[0]: # the last function
			self.add_fn_to_file(file, fn_stat)

	def add_fn_to_file(self, file, fn):
		if self.cur_proj not in self.stats:
			self.stats[self.cur_proj] = {}
		proj = self.stats[self.cur_proj]
		if file not in proj:
			proj[file] = []
		proj[file].append(fn.copy())

	def show_results(self):
		total_file = 0
		total_fn = 0
		total_unsafe_fn = 0
		precents = {}
		# print(self.stats)
		for proj, files in self.stats.items():
			total_file += len(files)
			precents[proj] = [0, 0, 0]
			pre = precents[proj]
			for fi, fns in files.items():
				total_fn += len(fns)
				pre[0] += len(fns)
				cur_unsafe = 0
				for item in fns:
					if item[1] > 0:
						total_unsafe_fn +=1
						cur_unsafe += 1
				pre[1] += cur_unsafe
				# if cur_unsafe > 0:
				# 	print(fi, len(fns), cur_unsafe)
			pre[2] = pre[1] * 100 / pre[0]
		print(f"Total files: {total_file}")
		print(f"Total functions (unsafe/total): {total_unsafe_fn}/{total_fn}")
		rate = total_unsafe_fn * 100 / total_fn 
		print("Unsafe function rate: %0.2f%%" % rate)
		print(precents)
		p1 = {0: 0, 30: 0, 60: 0, 100: 0}
		p2 = {0: 0, 20: 0, 40: 0, 60: 0, 80: 0, 100: 0}
		p1k = sorted(p1.keys())
		p2k = sorted(p2.keys())
		for proj, item in precents.items():
			r = 0
			for k in p1k:
				r = k
				if item[2] <= k:
					break
			p1[r] += 1
			for k in p2k:
				r = k
				if item[2] <= k:
					break
			p2[r] += 1
		r = 0
		print(p1)
		for k in p1k:
			print(f"{r} - {k}: {p1[k]}")
			r = k
		r = 0
		print(p2)
		for k in p2k:
			print(f"{r} - {k}: {p2[k]}")
			r = k

	def gen_report(self):
		pass

def main():
	if len(sys.argv) < 2:
		Utils.err_print("Usage: python3 stat_unsafe.py [directory]|-d")
	arg = sys.argv[1]
	if arg == "-d":
		CodeFetch(50)
	elif arg == "-r":
		cf = CheckFile(SAVED_PATH)
		cf.show_results()

if __name__ == "__main__":
	main()
